package robocup.component.tactics;

import java.util.LinkedList;

import robocup.component.Prophet;
import robocup.component.RobocupAgent;
import robocup.component.SConf;
import robocup.component.WorldModel;
import robocup.component.actions.ActionSet;
import robocup.component.tactics.StateCache.BOOLEANS;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;

/**
 * This class contains all of the players states and offers methods for
 * calculating the best state.
 */
public class StateEvaluation {

  /**
   * all states
   */
  protected LinkedList<AbstractState> permanentStates;

  /**
   * the world.
   */
  protected WorldModel              world;

  /**
   * the prophet.
   */
  protected Prophet                   prophet;

  /**
   * the player capable of all the states.
   */
  protected Player                    player;

  /**
   * Ref. to the ball
   */
  protected Ball                      ball;

  /**
   * the assessment of the max. state
   */
  protected double                    maxStateAssessment = 0.0;

  /**
   * all states with a true precondition in a given cycle.
   */
  protected LinkedList<AbstractState> possibleStates     = new LinkedList<AbstractState>();

  protected ActionSet                 bestActions        = null;

  protected RobocupAgent agent;
  
  protected final StateCache cache= new StateCache();
  
  /**
   * This class is the basic state-selecting component. It evaluates all
   * possible states and selects the 'best' for the given agent thereafter.
   * 
   * StateEvaluation must be initalized: It refers to the StateTactic
   * (neccessary?) and to the RobocupClient, which is used to send actions!
   * 
   * @param parent -
   *          the StateTactic holds the evaluation
   * @param agent -
   *          the agent object
   * 
   * @return the StateEvaluation
   */
  public StateEvaluation(final RobocupAgent agent) {

    this.agent = agent;
    this.prophet = agent.getProphet();
    this.permanentStates = new LinkedList<AbstractState>();

    if (agent != null) {
      this.world = agent.getWorld();
      this.player = this.world.getSelfRef();
    }
  }

  /**
   * adds a State to the State Evaluation. (used for loading all states from the
   * player's *.conf file)
   * 
   * @param state -
   *          the state
   */
  public void addState(final AbstractState state) {

    if (!this.permanentStates.contains(state)) {
      this.permanentStates.add(state);
    }
  }

  /**
   * calculates the state with the max assessment and stores the result to
   * <code>#maxStateAssessment</code> and <code>#possibleStates</code>
   * 
   * @param p -
   *          player
   * @param b -
   *          ball
   */
  public void calculateBestState(final Player p,
      final Ball b) {

    cache.resetAll();
    this.player = p;
    this.ball = b;
    this.possibleStates.clear();    
    this.maxStateAssessment = 0.0;
    this.bestActions  = null;
    
    double currentAssessment;
    
    for (final AbstractState state : this.permanentStates) {
      state.reset();
      
      if(this.getProphet().getParent().getSynchro().wasSent()){
        break;
      }
      
      if (state.preCondition()) {
        currentAssessment = state.getStateAssessment();
        
        if(currentAssessment > this.maxStateAssessment){
          this.bestActions = state.getActionSet();
          this.maxStateAssessment = currentAssessment;
        }
        
        this.addPossibleStateToList(state);
      }
    }
  }

  /**
   * adds a state to the possible states. the possible state list remains
   * ordered.
   * 
   * @param state -
   *          the state to add
   */
  protected synchronized void addPossibleStateToList(final AbstractState state) {

    final double assessment = state.getStateAssessment();

    int i = 0;
    for (; i < this.possibleStates.size(); i++) {
      if (this.possibleStates.get(i).getStateAssessment() < assessment) {
        break;
      }
    }
    this.possibleStates.add(i, state);
  }

  /**
   * @return all the possible states.
   */
  public synchronized LinkedList<AbstractState> getPossibleStates() {

    return this.possibleStates;
  }

  /**
   * the best state. (the state with the maximum assessment)
   * 
   * @return
   */
  public AbstractState getBestState() {

    if (this.possibleStates.size() == 0) {
      return null;
    }
    return this.possibleStates.get(0);
  }

  /**
   * ************** SIMPLE GETTER/SETTER ********************
   */

  /**
   * @return the {@link SConf}
   */
  public SConf getSConf() {

    return SConf.getInstance();
  }

  /**
   * @return the world
   */
  public WorldModel getWorld() {

    return this.world;
  }

  /**
   * @return Returns the prophet.
   */
  public Prophet getProphet() {

    return this.prophet;
  }

  /**
   * @return Returns the player.
   */
  public Player getPlayer() {

    return this.player;
  }

  /**
   * @param player
   *          The player to set.
   */
  public void setPlayer(final Player player) {

    this.player = player;
  }

  /**
   * @return Returns the ball.
   */
  public Ball getBall() {

    return this.ball;
  }

  /**
   * @param ball
   *          The ball to set.
   */
  public void setBall(final Ball ball) {

    this.ball = ball;
  }

  /**
   * @return Returns the maxStateAssessment.
   */
  public double getMaxStateAssessment() {

    return this.maxStateAssessment;
  }

  
  /**
   * The bestActions.
   *
   * @return the bestActions
   */
  public ActionSet getBestActions() {
  
    return this.bestActions;
  }

  
  /**
   * The agent.
   *
   * @return the agent
   */
  public RobocupAgent getAgent() {
  
    return this.agent;
  }

  /**
   * This Method checks, if the value for hasBall is equal to the situation
   * recognized in the worldmodel
   * 
   * @return true, if hasBall is equal to the situation
   */
  protected boolean checkPlayerHasBall() {
    Boolean b= cache.get(BOOLEANS.PLAYER_HAS_BALL);
    if (b != StateCache.BOOLEAN_UNKNOWN) {
      return b.booleanValue();
    }
    if (getPlayer().canKick(getBall())) {
      cache.set(BOOLEANS.PLAYER_HAS_BALL, true);
      return true;
    }
    cache.set(BOOLEANS.PLAYER_HAS_BALL,false);
    return false;
  }

  /**
   * @return true if the player can kick and an opponent can kick at the same
   *         time or of all teammates that can kick he has the smallest number
   *         (prevents that effect of two teammates kciking the same ball)
   */
  protected boolean checkMyBall() {
    Boolean b= cache.get(BOOLEANS.MY_BALL);
    if (b != StateCache.BOOLEAN_UNKNOWN) {
      return b.booleanValue();
    }
    
    if (this.checkPlayerHasBall() && this.getProphet().getPlayersThatCanKick()
        .size() < 2 && !this.getPlayer().kickedStandard()) {
      cache.set(BOOLEANS.MY_BALL, true);
      return true;
    }

    if (!this.checkPlayerHasBall() || this.getProphet().getPlayersThatCanKick()
        .size() == 0 || this.getPlayer().kickedStandard()) {

      cache.set(BOOLEANS.MY_BALL, false);
      return false;
    }

    final LinkedList<Player> playersWithBall = this.getProphet()
        .getPlayersThatCanKick();

    int smallestNumber = 12;

    for (final Player p : playersWithBall) {
      if (!p.isFriend()) {
        cache.set(BOOLEANS.MY_BALL, true);
        return true;
      }
      if (p.getNumber() < smallestNumber) {
        smallestNumber = p.getNumber();
      }
    }
    if (this.getPlayer().getNumber() == smallestNumber) {
      cache.set(BOOLEANS.MY_BALL, true);
      return true;
    }

    cache.set(BOOLEANS.MY_BALL, false);
    return false; 
  }
  
  public boolean checkTeammateCanKick() {
	LinkedList<Player> players = this.getProphet().getPlayersThatCanKick();
	if (players.size() == 0) {
        return false;
	} else {
		for (Player p: players) {
			if (p.isFriend()) {
	            return true;
			}
		}
        return false;
	}
  }

}
